#include <stdio.h>
#include <stdlib.h>
#include <string.h> 
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 1024
#define MAX_ARGC 64
//.......................................................
//重定向ls -a -l > log.txt
#define NoneRedir -1
#define StdinRedir 0
#define StdoutRedir 1
#define AppendRedir 2

#define STREND '\0'
#define IgnoreSpace(buffer,pos) do{ while(buffer[pos] == ' ') pos++;}while(0)
int redir_type = NoneRedir;
char* filename = NULL;

void CheckRedir(char in[])
{
  //ls -a -l
  //ls -a -l > log.txt
  //ls -a -l >> log.txt
  //cat < log.txt
  redir_type = NoneRedir;
  filename = NULL;
  int pos = strlen(in) - 1;//遍历串
  while(pos >= 0)
  {
    if(in[pos] == '>')
    {
      if(in[pos-1] == '>')
      {
        redir_type = AppendRedir;
        in[pos-1] = STREND;
        pos++;//跳过第二个>
        IgnoreSpace(in,pos);
        filename = in + pos;
        break;
      }
      else
      {
        redir_type = StdoutRedir;
        in[pos++] = STREND;
        IgnoreSpace(in,pos);
        filename = in + pos;
        break;
      }
    }
    else if(in[pos] == '<')
    {
      redir_type = StdinRedir;//重定向类型
      in[pos++] = STREND;
      IgnoreSpace(in,pos);//忽略空格
      filename = in + pos;//要重定向的文件
      break;
    }
    else
    {
      pos--;
    }
  }
}
//.......................................................

const char* info[3];
char* argv[MAX_ARGC];//保存分隔字串
char pwd[SIZE];//更新环境变量

char env[SIZE];//环境变量表

int lastcode = 0;

void getInfo()
{
  char* user = getenv("USER");
  if(user) info[0] = user;
  else info[0] = "None";
  
  char* hostname = getenv("HOSTNAME");
  if(hostname) info[1] = hostname;
  else info[1] = "None";

  char* pwd = getenv("PWD");
  if(pwd) info[2] = pwd;
  else info[2] = "None";
}

int interactive(char out[], int size)
{
 //输出提示符并获取用户输入的命令字符串"ls -a -l"
  getInfo();
  printf("[%s@%s %s]$ ",info[0],info[1],info[2]);
  
  //获取用户输入的字符串
  //scanf("%s",commandline);
  fgets(out,size,stdin);
  //将\n修改成\0
  out[strlen(out) - 1] = '\0';//也能处理空串的情况
  
  return strlen(out);
}

void Split(char in[])
{
  //ls -a -l > log.txt
  CheckRedir(in);
  int i = 0;
  argv[i++] = strtok(in, " ");
  while(argv[i++] = strtok(NULL, " "));//这里写成=,不是==
  if(strcmp(argv[0],"ls") == 0)
  {
    argv[i-1] = (char*)"--color";
    argv[i] = NULL;
  }
}

void Execute()
{
  pid_t id = fork();
  if(id == 0)
  {
    int fd = -1;
    if(redir_type== StdinRedir)
    {
      fd = open(filename,O_RDONLY);
      dup2(fd,0);
    }
    else if(redir_type == StdoutRedir)
    {
      fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
      dup2(fd,1);
    }
    else if(redir_type == AppendRedir)
    {
      fd = open(filename,O_WRONLY|O_CREAT|O_APPEND);
      dup2(fd,1);
    }
    else
    {
      // do nothing
    }
    //子进程执行命令
    execvp(argv[0],argv);
    exit(1);
  }
  //父进程回收子进程
  int status = 0;
  pid_t rid = waitpid(id,&status,0);
  if(rid == id) lastcode = WEXITSTATUS(status);//获取进程退出码
}
int BuildCmd()
{
  int ret = 0;
  //1.检查是否是内键命令,是1,否0
  if(strcmp(argv[0],"cd") == 0)
  {
    ret = 1;
    //执行cd命令
    char* target = argv[1];//cd xxx or cd
    if(!target) target = getenv("HOME"); 
    chdir(target);//更改当前进程的工作目录
    char temp[1024];
    getcwd(temp,1024);//使用绝对路径
    snprintf(pwd,SIZE,"PWD=%s",temp);//更新环境变量
    putenv(pwd);
  }
  else if(strcmp(argv[0],"export") == 0)
  {
    ret = 1;
    if(argv[1]) 
    {
      strcpy(env,argv[1]);
      putenv(env);//导入环境变量
    }
  }
  else if(strcmp(argv[0],"echo") == 0)
  {
    ret = 1;
    if(argv[1] == NULL)
    {
      printf("\n");//输出空行
    }
    else 
    {
      if(argv[1][0] == '$')
      {
        if(argv[1][1] == '?')
        {
          printf("%d\n",lastcode);//输出退出码
          lastcode = 0;
        }
        else
        {
          char* e = getenv(argv[1] + 1);
          if(e) printf("%s\n",e);
        }
      }
      else
      { 
        printf("%s\n",argv[1]);//输出字符串
      }
    }
  }
  return ret;
}
int main()
{
  while(1)
  {
    //1.打印命令行提示符，获取用户输入的令字符串
    char commandline[SIZE];
    int n = interactive(commandline, SIZE);
    if(!n) continue;

    //2.对命令行字符串进行分隔
    Split(commandline);
  
    //3.处理内键命令 - 由父进程执行
    n = BuildCmd();
    if(n) continue;
    
    //4.执行命令
    Execute();
  }
  return 0;
}
